
<template>
  <view v-show="show" ref="cropper" class="vue-cropper" :style="{ top : `${containerTop}px` }">
    <view class="cropper-box">
      <view
        class="cropper-box-canvas"
        :style="{
          'width': imageWidth + 'px',
          'height': imageHeight + 'px',
          'transform': 'scale(' + scale + ',' + scale + ') ' + 'translate3d('+ x / scale + 'px,' + y / scale + 'px,' + '0)'
            + 'rotateZ('+ rotate * 90 +'deg)'
        }"
        @touchstart.stop.prevent="imgTouchStart"
        @touchmove.stop.prevent="imgMoveing"
        @touchend.stop.prevent="imgMoveEnd"
      >
        <image ref="cropperImg" :src="src" alt="cropper-img" mode="scaleToFill" class="uni-image" />
      </view>
    </view>
    <view class="cropper-drag-box cropper-modal cropper-move pointer-events" />
    <view class="cropper-crop-box" :class="{'pointer-events': cropFixed}" :style="{'width': cropW + 'px','height': cropH + 'px','transform': 'translate3d('+ cropOffsertX + 'px,' + cropOffsertY + 'px,' + '0)'}">
      <view class="cropper-view-box">
        <image :style="{'width': imageWidth + 'px','height': imageHeight + 'px','transform': 'scale(' + scale + ',' + scale + ') ' + 'translate3d('+ (x - cropOffsertX) / scale + 'px,' + (y - cropOffsertY) / scale + 'px,' + '0)' + 'rotateZ('+ rotate * 90 +'deg)'}" mode="scaleToFill" :src="src" alt="cropper-img" />
      </view>

      <view v-if="!cropFixed" class="cropper-face cropper-move" @touchstart.stop.prevent="touchStart" @touchmove.stop.prevent="cropMoveing" />

      <view class="crop-line line-w" />
      <view class="crop-line line-a" />
      <view class="crop-line line-s" />
      <view class="crop-line line-d" />
      <block v-if="!cropFixed">
        <view class="crop-point point-lt" @touchstart.stop.prevent="touchStart" @touchmove.stop.prevent="dragMove($event, 'left-top')" />
        <view class="crop-point point-mt" @touchstart.stop.prevent="touchStart" @touchmove.stop.prevent="dragMove($event, 'middle-top')" />
        <view class="crop-point point-rt" @touchstart.stop.prevent="touchStart" @touchmove.stop.prevent="dragMove($event, 'right-top')" />
        <view class="crop-point point-ml" @touchstart.stop.prevent="touchStart" @touchmove.stop.prevent="dragMove($event, 'middle-left')" />
        <view class="crop-point point-mr" @touchstart.stop.prevent="touchStart" @touchmove.stop.prevent="dragMove($event, 'middle-right')" />
        <view class="crop-point point-lb" @touchstart.stop.prevent="touchStart" @touchmove.stop.prevent="dragMove($event, 'left-bottom')" />
        <view class="crop-point point-mb" @touchstart.stop.prevent="touchStart" @touchmove.stop.prevent="dragMove($event, 'middle-bottom')" />
        <view class="crop-point point-rb" @touchstart.stop.prevent="touchStart" @touchmove.stop.prevent="dragMove($event, 'right-bottom')" />
      </block>
    </view>

    <canvas canvas-id="myCanvas" class="cropper-canvas" :style="{ 'width': cropW + 'px','height': cropH + 'px' }" />

    <view class="btn-group">
      <view v-show="showResetBtn" class="btn-item reset-btn" @tap="init" />
      <view v-show="showRotateBtn" class="btn-item rotate-btn" @tap="rotateHandler" />
    </view>

    <view class="uni-info__ft">
      <view class="uni-modal__btn uni-modal__btn_default" style="color: rgb(0, 0, 0);" @tap="cancel">取消</view>
      <view class="uni-modal__btn uni-modal__btn_primary" style="color: rgb(0, 122, 255);" @tap="confirm">确定</view>
    </view>
  </view>
</template>

<script>
/* eslint-disable */
export default {
  name: 'ImageCropper',
  props: {
    cropWidth: {
      type: Number,
      default: 200
    },
    cropHeight: {
      type: Number,
      default: 200
    },
    cropFixed: {
      type: Boolean,
      default: false
    },
    src: {
      type: String
    },
    showResetBtn: {
      type: Boolean,
      default: true
    },
    showRotateBtn: {
      type: Boolean,
      default: true
    }
  },
  data() {
    const sysInfo = uni.getSystemInfoSync()
    const pixelRatio = sysInfo.pixelRatio
    return {
      show: false,
      scale: 1,
      rotate: 0,
      cropW: 0,
      cropH: 0,
      cropOldW: 0,
      cropOldH: 0,
      sysInfo: sysInfo,
      pixelRatio: pixelRatio,
      imageRealWidth: 0,
      imageRealHeight: 0,
      cropOffsertX: 0,
      cropOffsertY: 0,
      startX: 0,
      startY: 0,
      // 裁剪框与边界间距
      border: 5,
      x: 0,
      y: 0,
      startL: 0,
      oldScale: 1
    }
  },
  computed: {
    containerTop() {
      let top = 0
      // #ifdef H5
      top = 44
      // #endif
      return top
    },
    // 容器高度
    containerHeight() {
      return this.windowHeight - 48
    },
    // 屏幕宽度
    windowWidth() {
      return this.sysInfo.windowWidth
    },
    windowHeight() {
      return this.sysInfo.windowHeight
    },
    // 图片宽高比
    imageRatio() {
      if (this.imageRealHeight > 0) {
        return this.imageRealWidth / this.imageRealHeight
      }
      return 0
    },
    // 等比缩放后的宽度
    imageWidth() {
      if (this.imageRatio >= 1) {
        return this.windowWidth
      }
      return this.windowWidth * this.imageRatio
    },
    // 等比缩放后的高度
    imageHeight() {
      if (this.imageRatio >= 1) {
        return this.windowWidth / this.imageRatio
      }
      return this.windowWidth
    }
  },
  watch: {
    src(val) {
      if (val.length > 0) {
        this.init()
      }
    },
    show(val) {
      if (!val) {
        this.src = ''
      }
    }
  },
  methods: {
    rotateHandler() {
      if (this.rotate == 3) {
        this.rotate = 0
      } else {
        ++this.rotate
      }
    },
    init() {
      this.rotate = 0
      this.scale = 1
      this.cropW = this.cropWidth
      this.cropH = this.cropHeight
      uni.showLoading({
        title: '图片加载中...'
      })
      this.loadImage(this.src).then((e) => {
        uni.hideLoading()
      }).catch((e) => {
        uni.hideLoading()
        uni.showModal({
          title: '标题',
          content: '图片加载失败'
        })
      })
    },
    loadImage(src) {
      const _this = this
      return new Promise((resolve, reject) => {
        uni.getImageInfo({
          src: src,
          success: (res) => {
            _this.imageRealWidth = res.width
            _this.imageRealHeight = res.height

            _this.cropOffsertX = _this.windowWidth / 2 - _this.cropW / 2
            _this.cropOffsertY = _this.windowHeight / 2 - _this.cropH / 2
            _this.show = true

            _this.$nextTick(() => {
              _this.x = _this.windowWidth / 2 - _this.imageWidth / 2
              _this.y = _this.containerHeight / 2 - _this.imageHeight / 2
            })
            resolve(res)
          },
          fail: (e) => {
            _this.show = false
            reject(e)
          }
        })
      })
    },
    cancel() {
      this.show = false
      this.$emit('cancel')
    },
    confirm(event) {
      uni.showLoading({
        title: '裁剪中...'
      })
      const _this = this
      const ctx = uni.createCanvasContext('myCanvas', _this)

      const pixelRatio = _this.pixelRatio
      const imgage = _this.src
      const imgW = _this.imageWidth * _this.scale
      const imgH = _this.imageHeight * _this.scale
      const rotate = _this.rotate
      let dx = _this.cropOffsertX - _this.x - (_this.imageWidth - imgW) / 2
      let dy = _this.cropOffsertY - _this.y - (_this.imageHeight - imgH) / 2

      ctx.setFillStyle('white')
      ctx.fillRect(0, 0, imgW, imgH)
      ctx.save()

      ctx.rotate((rotate * 90 * Math.PI) / 180)
      switch (rotate) {
        case 1:
          dx += (imgH - imgW) / 2
          dy -= (imgH - imgW) / 2
          ctx.drawImage(imgage, -dy, dx, imgW, -imgH)
          break
        case 2:
          ctx.drawImage(imgage, dx, dy, -imgW, -imgH)
          break
        case 3:
          dx += (imgH - imgW) / 2
          dy -= (imgH - imgW) / 2
          ctx.drawImage(imgage, dy, -dx, -imgW, imgH)
          break
        default:
          ctx.drawImage(imgage, -dx, -dy, imgW, imgH)
          break
      }
      ctx.restore()
      ctx.draw(false, () => {
        uni.canvasToTempFilePath({
          canvasId: 'myCanvas',
          destWidth: _this.cropW * pixelRatio,
          destHeight: _this.cropH * pixelRatio,
          success: (res) => {
            uni.hideLoading()
            event.detail.tempFilePath = res.tempFilePath
            _this.show = false
            _this.$emit('confirm', event)
          },
          fail: (e) => {
            uni.hideLoading()
            uni.showModal({
              title: '提示',
              content: '裁剪失败'
            })
          }
        }, _this)
      })
    },
    imgTouchStart(e) {
      if (e.touches.length == 2) {
        this.oldScale = this.scale
        this.scaling = true
        const x = e.touches[0].pageX - e.touches[1].pageX
        const y = e.touches[0].pageY - e.touches[1].pageY
        const hypotenuse = Math.sqrt(
          Math.pow(x, 2) +
                        Math.pow(y, 2)
        )

        this.startL = Math.max(x, y, hypotenuse)
        uni.showModal({
          content: this.startL
        })
      } else {
        this.startX = e.touches[0].pageX - this.x
        this.startY = e.touches[0].pageY - this.y
      }
    },
    imgMoveing(e) {
      if (this.scaling) {
        let scale = this.oldScale

        const x = e.touches[0].pageX - e.touches[1].pageX
        const y = e.touches[0].pageY - e.touches[1].pageY
        const hypotenuse = Math.sqrt(
          Math.pow(x, 2) +
                        Math.pow(y, 2)
        )

        const newL = Math.max(x, y, hypotenuse)

        const cha = newL - this.startL

        // 根据图片本身大小 决定每次改变大小的系数, 图片越大系数越小
        // 1px - 0.2
        let coe = 1
        coe =
                        coe / this.imageWidth > coe / this.imageHeight
                          ? coe / this.imageHeight
                          : coe / this.imageWidth
        coe = coe > 0.1 ? 0.1 : coe
        const num = coe * cha

        if (cha > 0) {
          scale += Math.abs(num)
        } else if (cha < 0) {
          scale > Math.abs(num) ? (scale -= Math.abs(num)) : scale
        }

        this.scale = scale
      } else {
        const moveX = e.touches[0].pageX - this.startX
        const moveY = e.touches[0].pageY - this.startY

        this.x = moveX
        this.y = moveY
      }
    },
    imgMoveEnd() {
      setTimeout(() => {
        this.scaling = false
      }, 100)
    },
    touchStart(e) {
      this.startX = e.touches[0].pageX - this.cropOffsertX
      this.startY = e.touches[0].pageY - this.cropOffsertY

      this.cropOldW = this.cropW
      this.cropOldH = this.cropH
    },
    cropMoveing(e) {
      const moveX = this._cropX(e.touches[0].pageX - this.startX)
      const moveY = this._cropY(e.touches[0].pageY - this.startY)

      this.cropOffsertX = moveX
      this.cropOffsertY = moveY
    },
    dragMove(e, type) {
      if (this.cropFixed) {
        return false
      }
      const moveX = e.touches[0].pageX - this.startX
      const moveY = e.touches[0].pageY - this.startY
      switch (type) {
        case 'left-top':
          this._cropMoveLeft(moveX)
          this._cropMoveTop(moveY)
          break
        case 'middle-top':
          this._cropMoveTop(moveY)
          break
        case 'right-top':
          this._cropMoveTop(moveY)
          this._cropMoveRight(moveX)
          break
        case 'middle-right':
          this._cropMoveRight(moveX)
          break
        case 'right-bottom':
          this._cropMoveRight(moveX)
          this._cropMoveBottom(moveY)
          break
        case 'middle-bottom':
          this._cropMoveBottom(moveY)
          break
        case 'left-bottom':
          this._cropMoveBottom(moveY)
          this._cropMoveLeft(moveX)
          break
        case 'middle-left':
          this._cropMoveLeft(moveX)
          break
        default:
          break
      }
    },
    _cropMoveTop(y) {
      this.cropH += this.cropOffsertY - y
      this.cropOffsertY = y
    },
    _cropMoveRight(x) {
      this.cropW = this.cropOldW + (x - this.cropOffsertX)
    },
    _cropMoveBottom(y) {
      this.cropH = this.cropOldH + (y - this.cropOffsertY)
    },
    _cropMoveLeft(x) {
      this.cropW += this.cropOffsertX - x
      this.cropOffsertX = x
    },
    _cropX(x) {
      if (x <= 0) {
        return this.border
      }
      if (x + this.cropW >= this.windowWidth) {
        return this.windowWidth - this.cropW - this.border
      }
      return x
    },
    _cropY(y) {
      if (y <= 0) {
        return this.border
      }
      if (y + this.cropH >= this.windowHeight) {
        return this.windowHeight - this.cropH - (this.sysInfo.statusBarHeight > 0 ? 0 : 44) - this.border
      }
      return y
    }
  }
}
</script>

<style scoped lang="css">

    @font-face {
        font-family: "iconfont";
        src: url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAR4AAsAAAAACKgAAAQsAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDBgqEfIRGATYCJAMMCwgABCAFhG0HShugB8gOJUHBwAAAAAFEBNmwzd4dtatSmmpFoVAEhUThEAYkCozFKDCqCVO6RfH/89v869awDnTR1qrSANFt4GG4SNxreBn91fmV9f3+53J613ieHba+N1zmGM8PA7oXTaCAxpjei8IoLWFsGLu4jPME6vWJJdovqmgAO4U2LRBnep0K7GJmpYQWanXVOWuLuAFrtenK4haAa/f38QnKsCOpyrRFh6eFWsh5KXnfYcn958BGQNKfE8wmMmaAQpzkuo9Z+ukZluoltVV5abUipL5i/ysArlhWVut/eCRBVNPUjYg6oUo7JTHFoaYDSvdacnKTq9GAB4AY5y2dtL3qpFh1DENdnJC6Hq+xYb7pyRMDMzc/fYoJjY8flwO3m98rMucF+IZHj6Cagw5UeKpxyFbt2rHGY/8jpa7CYMvLfcIesLjY3bdqhaf+nqgQs2qT/+rjCH/VfA0VFGuAC3iE8NEr/Vau8vZsXiUy7+V3c3tQQXMAuNjDCC89KDIHH0OFhnUi81GEPwyc7wZUaN7DnUf4g+ZLQsMKYV/94NjK7R7TEM4niTY1oJ5zEU62aNVaasUub08YLUEam5EnT6a61/I17dNk+vTu9jpJjXhsTFwjqTtpCBxBIIgS6iQnc/Zod1YGKp0rAwsD8kkyP6AwcK0hcAwkiQmBhWvxPZWKDu86aUH2nLEdi9rGX1eXq5P6A1SrnAucMVMdZH/GKi/jyfCqJyucfK3mXpVujXOPfFf5LC4Dvx0X/943JyOq4HuCTZ8KiIPPAb6ro8akpT6ufiq39BQrNlk5mp8pO0JlJLk8f5QalRjoP60IMx0N8n7wGhSD3n6/F1zlcTVz/cR+Ev0lkLSTd7UiPbD/wCxGRMA2Krwro2O0bTQtImbwhjAJc0S3N4ROx15/PH60IzaIOjCbEelqkDOfETNxb/FMixnWNzeJp2KPQw9A5d76jGUOQOUvH7RE/o2RfkNatd3OGf9q0QKbnq8WB7qy+hVqJRjJn1BQgP/iErks0yy5iGJTrOayW7C/z0IoZH0qNH+7N+31XXc7G2p1hZDU6IWs1ghaqDNQpcEKVKu1BfWmFW9u0IFhKUodpswCEFodgqTZHWStbqOF+hqqdPsG1VrDEuodhfueDcZCj+QzuIrFtZh6BNNraIowbCzi1dbhOlOfionKXHoTzgzoY5hCKk/minEKZ/pYMDCoU7IsgREM3Y8Vgcvwvj4aMzK0AdewUpJljWkyGZH3IKmG7gfEHgZOhYXTwqiNwOhp0CiE3ZiFpL5fB6dj0keFKcGV+JvgGAP0vWMUpOQ10GI1VQt3LoMHDNJRYrEIPInAoPXDFEEnrk9P0zDG/FEGOA2WFNkiaZRGhuoRddXS8bX917cL6mn9c6TIUXSekybKHKQfJXFq2KSiRklLYU8dNKWDIX0cAA==') format('woff2');
    }

    .vue-cropper {
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 998;
        box-sizing: border-box;
        user-select: none;
        -webkit-user-select: none;
        -moz-user-select: none;
        -ms-user-select: none;
        direction: ltr;
        touch-action: none;
        text-align: left;
        background-image: url("");
    }

    .cropper-canvas {
        position: absolute;
        top: -9999px;
        left:-9999px;
        z-index: -998;
    }

    .vue-cropper .uni-info__ft {
        position: absolute;
        line-height: 48px;
        font-size: 18px;
        display: -webkit-box;
        display: -webkit-flex;
        display: flex;
        bottom: 0;
        left: 0;
        right: 0;
        z-index: 998;
    }

    .btn-group {
        position: absolute;
        right: 30px;
        bottom: 78px;
        z-index: 998;
    }

    .btn-item {
        position: relative;
        width: 40px;
        height: 40px;
        background: #fff;
        border-radius: 20px;
        padding: 10px;
        display: inline-block;
        margin-left: 10px;
    }

    .btn-item:active {
        background: #ccc;
    }

    .rotate-btn {
        font-family: "iconfont" !important;
        font-size: 24px;
        font-style: normal;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        line-height: 20px;
    }

    .rotate-btn:before {
        content: "\e65c";
        margin-left: -2px;
    }

    .reset-btn {
        font-family: "iconfont" !important;
        font-size: 24px;
        font-style: normal;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        line-height: 20px;
    }

    .reset-btn:before {
        content: "\e648";
        margin-left: -2px;
    }

    .vue-cropper .uni-info__ft:after {
        content: " ";
        position: absolute;
        left: 0;
        top: 0;
        right: 0;
        height: 1px;
        border-top: 1px solid #d5d5d6;
        color: #d5d5d6;
        -webkit-transform-origin: 0 0;
        transform-origin: 0 0;
        -webkit-transform: scaleY(.5);
        transform: scaleY(.5);
        z-index: 998;
    }

    .vue-cropper .uni-modal__btn {
        display: block;
        -webkit-box-flex: 1;
        -webkit-flex: 1;
        flex: 1;
        color: #3cc51f;
        text-decoration: none;
        -webkit-tap-highlight-color: rgba(0,0,0,0);
        position: relative;
        text-align: center;
        background-color: #fff;
        z-index: 998;
    }

    .vue-cropper .uni-modal__btn:first-child:after { display:  none }
    .vue-cropper .uni-modal__btn:after {
        content: " ";
        position: absolute;
        left: 0;
        top: 0;
        width: 1px;
        bottom: 0;
        border-left: 1px solid #d5d5d6;
        color: #d5d5d6;
        -webkit-transform-origin: 0 0;
        transform-origin: 0 0;
        -webkit-transform: scaleX(.5);
        transform: scaleX(.5);
        z-index: 998;
    }

    .vue-cropper .uni-modal__btn:active {
        background-color: #eee;
    }

    .cropper-box,
    .cropper-box-canvas,
    .cropper-drag-box,
    .cropper-crop-box,
    .cropper-face {
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        user-select: none;
        z-index: 998;
    }

    .uni-image {
        width: 100%;
        height: 100%;
    }

    .cropper-box-canvas image {
        position: relative;
        text-align: left;
        user-select: none;
        transform: none;
        max-width: none;
        max-height: none;
        z-index: 998;
    }

    .cropper-box {
        overflow: hidden;
    }

    .cropper-move {
        cursor: move;
    }

    .cropper-crop {
        cursor: crosshair;
    }

    .cropper-modal {
        background: rgba(0, 0, 0, 0.5);
    }

    .pointer-events {
        pointer-events:none;
    }

    /* .cropper-crop-box {
        border: 2px solid #39f;
    } */

    .cropper-view-box {
        display: block;
        overflow: hidden;
        width: 100%;
        height: 100%;
        outline: 1px solid #39f;
        outline-color: rgba(51, 153, 255, 0.75);
        user-select: none;
    }

    .cropper-view-box image {
        user-select: none;
        text-align: left;
        max-width: none;
        max-height: none;
    }

    .cropper-face {
        top: 0;
        left: 0;
        background-color: #fff;
        opacity: 0.1;
    }

    .crop-line {
        position: absolute;
        display: block;
        width: 100%;
        height: 100%;
        opacity: 0.1;
        z-index: 998;
    }

    .line-w {
        top: -3px;
        left: 0;
        height: 5px;
        cursor: n-resize;
    }

    .line-a {
        top: 0;
        left: -3px;
        width: 5px;
        cursor: w-resize;
    }

    .line-s {
        bottom: -3px;
        left: 0;
        height: 5px;
        cursor: s-resize;
    }

    .line-d {
        top: 0;
        right: -3px;
        width: 5px;
        cursor: e-resize;
    }

    .crop-point {
        position: absolute;
        width: 8px;
        height: 8px;
        opacity: 0.75;
        background-color: #39f;
        border-radius: 100%;
        z-index: 998;
    }

    .point-lt {
        top: -4px;
        left: -4px;
        cursor: nw-resize;
    }

    .point-mt {
        top: -5px;
        left: 50%;
        margin-left: -3px;
        cursor: n-resize;
    }

    .point-rt {
        top: -4px;
        right: -4px;
        cursor: ne-resize;
    }

    .point-ml {
        top: 50%;
        left: -4px;
        margin-top: -3px;
        cursor: w-resize;
    }

    .point-mr {
        top: 50%;
        right: -4px;
        margin-top: -3px;
        cursor: e-resize;
    }

    .point-lb {
        bottom: -5px;
        left: -4px;
        cursor: sw-resize;
    }

    .point-mb {
        bottom: -5px;
        left: 50%;
        margin-left: -3px;
        cursor: s-resize;
    }

    .point-rb {
        bottom: -5px;
        right: -4px;
        cursor: se-resize;
    }
</style>
